<?php

namespace yunj\core;

use think\File;
use think\facade\Request;
use think\file\UploadedFile;

final class YunjFile {

    /**
     * 配置
     * @var array
     */
    private $config;

    private function __construct() {
        $this->config = Config::get('file.');
    }

    private static $instance;

    public static function getInstance() {
        if (!self::$instance instanceof self) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 检查类型
     * @param string $fileExt
     * @param array|string $limitExts
     * @return bool
     */
    private static function checkExt(string $fileExt, $limitExts): bool {
        $limitExts = is_array($limitExts) ? $limitExts : explode(',', $limitExts);
        return in_array($fileExt, $limitExts);
    }

    /**
     * 检查大小
     * @param int $fileSize 文件大小，单位字节
     * @param int|float|double $limitSize 限制大小，单位MB
     * @return bool
     */
    private static function checkSize(int $fileSize, $limitSize): bool {
        return $fileSize <= ($limitSize * 1024 * 1024);
    }

    /**
     * Notes: 上传
     * Author: Uncle-L
     * Date: 2020/9/1
     * Time: 17:30
     * @param string $from
     * @return mixed
     */
    public function upload($from = 'input') {
        $fromConfig = self::fromConfig($from);
        $requestKey = $fromConfig['request_key'];
        $urlCallback = $fromConfig['url_callback'];
        $errorCallback = $fromConfig['error_callback'];
        $successCallback = $fromConfig['success_callback'];
        // 上传文件
        $file = request()->file($requestKey);
        if (!$file || !($file instanceof File)) return call_user_func_array($errorCallback, ['未找到上传的文件！文件大小可能超过php.ini默认2M限制']);

        // 文件MIME类型
        $mime = $file->getMime();
        if ($mime == 'text/x-php' || $mime == 'text/html') return call_user_func_array($errorCallback, ['禁止上传php/html文件！']);

        $originalName = $file->getOriginalName();
        // 文件后缀
        $originalNameArr = explode('.', $originalName);
        $ext = array_pop($originalNameArr);
        // 文件大小
        $fileSize = $file->getSize();

        // 格式、大小校验
        if (($uploadImgExt = yunj_setting('sys.upload_img_ext')) && self::checkExt($ext, $uploadImgExt)) {
            $type = 'img';
            $uploadImgSize = yunj_setting('sys.upload_img_size');
            if (!self::checkSize($fileSize, $uploadImgSize)) return call_user_func_array($errorCallback, ["上传图片大小超过系统限制：{$uploadImgSize}MB"]);
        } else if (($uploadFileExt = yunj_setting('sys.upload_file_ext')) && self::checkExt($ext, $uploadFileExt)) {
            $type = 'file';
            $uploadFileSize = yunj_setting('sys.upload_file_size');
            if (!self::checkSize($fileSize, $uploadFileSize)) return call_user_func_array($errorCallback, ["上传文件大小超过系统限制：{$uploadFileSize}MB"]);
        } else if (($uploadMediaExt = yunj_setting('sys.upload_media_ext')) && self::checkExt($ext, $uploadMediaExt)) {
            $type = 'media';
            $uploadMediaSize = yunj_setting('sys.upload_media_size');
            if (!self::checkSize($mediaSize, $uploadMediaSize)) return call_user_func_array($errorCallback, ["上传资源大小超过系统限制：{$uploadMediaSize}MB"]);
        } else {
            return call_user_func_array($errorCallback, ['非系统允许的上传格式！']);
        }

        [$fileDirName, $fileName] = call_user_func_array($urlCallback, [$file]);

        // 判断是否开启七牛云
        if (yunj_setting('sys.qiniu_is_enable') == 'on') {
            $key = $type . DIRECTORY_SEPARATOR . date('Ymd');
            if ($fileDirName) {
                $key .= DIRECTORY_SEPARATOR . $fileDirName;
            }
            $key .= DIRECTORY_SEPARATOR . $fileName;
            try {
                $fileUrl = YunjQiniu::getInstance()->upload($key, $file->getRealPath())->url();
            } catch (\Exception $e) {
                return call_user_func_array($errorCallback, [$e->getMessage()]);
            }
        } else {
            // 本地上传
            $res = $this->checkUploadDirExist();
            if ($res !== true) return call_user_func_array($errorCallback, [$res]);
            $filePath = $type . DIRECTORY_SEPARATOR . date('Ymd');
            if ($fileDirName) {
                $filePath .= DIRECTORY_SEPARATOR . $fileDirName;
            }
            $fileFullPath = $this->config['upload_root_path'] . DIRECTORY_SEPARATOR . $filePath;
            // 上传
            $uploadFile = $file->move($fileFullPath, $fileName);
            if (!$uploadFile || !is_file($uploadFile->getRealPath())) {
                return call_user_func_array($errorCallback, ['文件上传失败！']);
            }
            $fileUrl = request()->domain() . '/upload/' . str_replace("\\", '/', $filePath) . '/' . $fileName;
        }

        $data = [
            'originalName' => $originalName,
            'url' => $fileUrl ?? '',
        ];
        return call_user_func_array($successCallback, [$data]);
    }

    /**
     * Notes: 校验[根目录\public\upload]是否存在
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 14:11
     * @return bool|string
     */
    private function checkUploadDirExist() {
        $uploadDirPath = $this->config['upload_root_path'];
        if (is_dir($uploadDirPath)) {
            $isWriteable = dir_is_writeable($uploadDirPath);
            if (!$isWriteable) return "文件上传失败！请赋予文件夹[根目录\\public\\upload]及其子目录写入权限";
        } else
            try {
                mkdir($uploadDirPath, 0755, true);
            } catch (\Exception $e) {
                return "请创建文件夹[根目录\\public\\upload]，并赋予[upload]文件夹写入权限";
            }
        return true;
    }

    /**
     * Notes: 来源配置
     * Author: Uncle-L
     * Date: 2020/9/1
     * Time: 17:30
     * @param $from
     * @param string $code
     * @param null $def
     * @return array|mixed|null
     */
    private static function fromConfig($from, $code = '', $def = null) {
        $param = [
            // 上传文件的参数key
            'request_key' => 'file',
            // 地址生成回调
            'url_callback' => function ($file) {
                // 文件目录名
                $fileDirName = '';
                // 文件名
                /** @var UploadedFile $originalName */
                $originalName = $file->getOriginalName();
                $originalNameArr = explode('.', $originalName);
                $ext = array_pop($originalNameArr);
                $filename = md5(msectime() . rand_char(6)) . '.' . $ext;
                return [$fileDirName, $filename];
            },
            // 错误返回回调
            'error_callback' => function ($msg = '') {
                return response_json(30001, $msg);
            },
            // 成功返回回调
            'success_callback' => function ($data = null) {
                return success_json($data);
            }
        ];
        switch ($from) {
            case 'input':
                $param = [
                        'error_callback' => function ($msg = '') {
                            return response_json(30001, $msg);
                        },
                        'success_callback' => function ($data = null) {
                            return success_json(['fileName' => $data['originalName'], 'url' => $data['url']]);
                        }
                    ] + $param;
                break;
            case 'nameUnchanged':
                // 名称不变
                $param = [
                        'url_callback' => function ($file) {
                            /** @var UploadedFile $originalName */
                            $originalName = $file->getOriginalName();
                            return [md5(msectime() . rand_char(6)), $originalName];
                        },
                        'success_callback' => function ($data = null) {
                            return success_json($data['url']);
                        }
                    ] + $param;
                break;
                break;
            case 'ckeditor':
                $param = [
                    'request_key' => 'upload',
                    'error_callback' => function ($msg = '') {
                        return json(['error' => ['message' => $msg, 'number' => 500]]);
                    },
                    'success_callback' => function ($data = null) {
                        return json(['fileName' => $data['originalName'], 'uploaded' => 1, 'url' => $data['url']]);
                    }
                ] + $param;
                break;
            case "editormd":
                $param = [
                    'request_key' => 'editormd-image-file',
                    'error_callback' => function ($msg = '') {
                        return json(['success' => 0, "message" => $msg]);
                    },
                    'success_callback' => function ($data = null) {
                        return json(['success' => 1, "message" => "成功上传", 'url' => $data['url']]);
                    }
                ] + $param;
                break;
        }
        return !$code ? $param : (isset($param[$code]) ? $param[$code] : $def);
    }

}